#include "Projections.h"


// Like gluLookAt
Matrix44 LookAt(Vector3Arg eyePos, Vector3Arg targetPos, Vector3Arg up)
{
    Vector3 zAxis = Normalize(eyePos - targetPos);
    Vector3 xAxis = Normalize(Cross(up, zAxis));
    Vector3 yAxis = Normalize(Cross(zAxis, xAxis));
    Vector3 eyeVec = -eyePos;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(xAxis.x, yAxis.x, zAxis.x, 0));
    matrix.SetRow(1, Vector4(xAxis.y, yAxis.y, zAxis.y, 0));
    matrix.SetRow(2, Vector4(xAxis.z, yAxis.z, zAxis.z, 0));
    matrix.SetRow(3, Vector4(MulVectorTransform(eyeVec, matrix), 1));
    return matrix;
}


// Like gluPerspective
Matrix44 PerspectiveProjection(float fieldOfViewY, float aspect, float nearClip, float farClip)
{
    float f = 1 / tanf(0.5f * fieldOfViewY);
    float e = (nearClip - farClip);
    float a = (farClip + nearClip) / e;
    float b = (2 * farClip * nearClip) / e;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(f/aspect, 0, 0,  0));
    matrix.SetRow(1, Vector4(       0, f, 0,  0));
    matrix.SetRow(2, Vector4(       0, 0, a, -1));
    matrix.SetRow(3, Vector4(       0, 0, b,  0));
    return matrix;
}


// Like gluPerspective with a far plane at infinity
Matrix44 PerspectiveProjection(float fieldOfViewY, float aspect, float nearClip)
{
    float f = 1 / tanf(0.5f * fieldOfViewY);
    float b = -2 * nearClip;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(f/aspect, 0,  0,  0));
    matrix.SetRow(1, Vector4(       0, f,  0,  0));
    matrix.SetRow(2, Vector4(       0, 0, -1, -1));
    matrix.SetRow(3, Vector4(       0, 0,  b,  0));
    return matrix;
}


// Like glFrustum
Matrix44 FrustumProjection(float left, float right, float bottom, float top, float nearClip, float farClip)
{
    float t = 2 * nearClip;
    float h = right - left;
    float v = top - bottom;
    float f = t / h;
    float g = t / v;
    float a = (right + left) / h;
    float b = (top + bottom) / v;
    float e = nearClip - farClip;
    float c = (farClip + nearClip) / e;
    float d = (2 * farClip * nearClip) / e;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(f, 0, 0,  0));
    matrix.SetRow(1, Vector4(0, g, 0,  0));
    matrix.SetRow(2, Vector4(a, b, c, -1));
    matrix.SetRow(3, Vector4(0, 0, d,  0));
    return matrix;
}


// Like glOrtho
Matrix44 OrthographicProjection(float left, float right, float bottom, float top, float nearClip, float farClip)
{
    float h = right - left;
    float v = top - bottom;
    float e = farClip - nearClip;
    float tx = -(right + left) / h;
    float ty = -(top + bottom) / v;
    float tz = -(farClip + nearClip) / e;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(2/h,   0,    0, 0));
    matrix.SetRow(1, Vector4(  0, 2/v,    0, 0));
    matrix.SetRow(2, Vector4(  0,   0, -2/e, 0));
    matrix.SetRow(3, Vector4( tx,  ty,   tz, 1));
    return matrix;
}


Matrix44 InverseOrthographicProjection(float left, float right, float bottom, float top, float nearClip, float farClip)
{
    float h = right - left;
    float v = top - bottom;
    float e = farClip - nearClip;
    float tx = (right + left) / 2;
    float ty = (top + bottom) / 2;
    float tz = (farClip + nearClip) / -2;
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(h/2,   0,    0, 0));
    matrix.SetRow(1, Vector4(  0, v/2,    0, 0));
    matrix.SetRow(2, Vector4(  0,   0, e/-2, 0));
    matrix.SetRow(3, Vector4( tx,  ty,   tz, 1));
    return matrix;
}


Matrix44 InversePerspectiveProjection(float fieldOfViewY, float aspect, float nearClip, float farClip)
{
    float f = tanf(0.5f * fieldOfViewY);
    float e = (nearClip - farClip);
    float b = e / (2 * farClip * nearClip);
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(aspect*f, 0,  0,            0));
    matrix.SetRow(1, Vector4(		0, f,  0,            0));
    matrix.SetRow(2, Vector4(		0, 0,  0,            b));
    matrix.SetRow(3, Vector4(		0, 0, -1, 1/nearClip+b));
    return matrix;
}


Matrix44 InverseFrustumProjection(float left, float right, float bottom, float top, float nearClip, float farClip)
{
    float f = (right-left) / (2*nearClip);
    float g = (top-bottom) / (2*nearClip);
    
    float a = (right+left) / (2*nearClip);
    float b = (top+bottom) / (2*nearClip);
    float d = (nearClip-farClip) / (2*farClip*nearClip);
    
    Matrix44 matrix;
    matrix.SetRow(0, Vector4(f, 0,  0, 0));
    matrix.SetRow(1, Vector4(0, g,  0, 0));
    matrix.SetRow(2, Vector4(0, 0,  0, d));
    matrix.SetRow(3, Vector4(a, b, -1, (nearClip+farClip)/(2*farClip*nearClip)));
    return matrix;
}
